package com.xinhe.web.weixin;

import com.xinhe.web.entity.WeixinConfig;
import com.xinhe.web.enums.WXPayConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
@Slf4j
public class WXPayUtil {
    private static final String CHARSET = "UTF-8";

    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); idx++) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == 1) {
                    Element element = (Element)node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {}
            return data;
        } catch (Exception ex) {
            log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }
    }

    public static String mapToXml(Map<String, String> data) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.newDocument();
        Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key : data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty("encoding", "UTF-8");
        transformer.setOutputProperty("indent", "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString();
        try {
            writer.close();
        } catch (Exception ex) {}

        return output;
    }

    public static String generateSignedXml(Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
    }

    public static String generateSignedXml(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        //签名类型，默认为MD5，支持HMAC-SHA256和MD5。
        String sign = generateSignature(data, key, signType);
        data.put("sign", sign);
        return mapToXml(data);
    }

    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey("sign")) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key).equals(sign);
    }

    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
    }


    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        if (!data.containsKey("sign")) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key, signType).equals(sign);
    }

    public static String generateSignature(Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WXPayConstants.SignType.MD5);
    }
    public static String generateSignature(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.<String>toArray(new String[keySet.size()]);
        Arrays.sort((Object[])keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (!k.equals("sign"))
            {

                if (((String)data.get(k)).trim().length() > 0) {
                    sb.append(k).append("=").append(((String)data.get(k)).trim()).append("&");
                }
            }
        }
        sb.append("key=").append(key);
        if (WXPayConstants.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        throw new Exception(String.format("Invalid sign_type: %s", new Object[] { signType }));
    }
    public static String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString(item & 0xFF | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString(item & 0xFF | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    public static long getCurrentTimestamp() {
        return System.currentTimeMillis() / 1000L;
    }
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
    public static String requestUnifiedorder(Map<String, String> data, WeixinConfig weixinConfig) throws Exception {
        return request("/pay/unifiedorder", data, weixinConfig, Boolean.FALSE);
    }

    public static String requestTransfer(Map<String, String> data, WeixinConfig weixinConfig) throws Exception {
        return request("/mmpaymkttransfers/promotion/transfers", data, weixinConfig, Boolean.TRUE);
    }

    private static String request(String urlSuffix, Map<String, String> reqData, WeixinConfig weixinConfig, Boolean useCert) throws Exception {
        BasicHttpClientConnectionManager connManager;
        if (useCert.booleanValue()) {
            char[] password = weixinConfig.getMchId().toCharArray();
            log.info("cert path: {}", weixinConfig.getCertPath());
            File certFile = new File(weixinConfig.getCertPath());
            log.info("cert file exists: {}", Boolean.valueOf(certFile.exists()));
            InputStream certStream = new FileInputStream(certFile);
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certStream, password);
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, password);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" }, null, (HostnameVerifier)new DefaultHostnameVerifier());
            connManager = new BasicHttpClientConnectionManager((Lookup) RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslConnectionSocketFactory).build(), null, null, null);
        }else {
            connManager = new BasicHttpClientConnectionManager((Lookup)RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(), null, null, null);
        }
        CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager((HttpClientConnectionManager)connManager).build();
        String requestXml = generateSignedXml(reqData, weixinConfig.getPayKey());
        log.info("requestXml=" + requestXml);
        String responseXml = null;
        try {
            String url = "https://api.mch.weixin.qq.com" + urlSuffix;
            HttpPost httppost = new HttpPost(url);
            StringEntity postEntity = new StringEntity(requestXml, "UTF-8");
            httppost.addHeader("Content-Type", "text/xml");
            httppost.setEntity((HttpEntity)postEntity);
            CloseableHttpResponse response = httpClient.execute((HttpUriRequest)httppost);
            try {
                HttpEntity resEntity = response.getEntity();
                responseXml = EntityUtils.toString(resEntity, "UTF-8");
                EntityUtils.consume(resEntity);
            } finally {
                response.close();
            }
        } finally {
            httpClient.close();
        }
        log.info("responseXml=" + responseXml);
        return responseXml;
    }
    public static String requestRefund(Map<String, String> data, WeixinConfig weixinConfig) throws Exception {
        return request("/secapi/pay/refund", data, weixinConfig, Boolean.TRUE);
    }




}
